ReactJS入门

Author: ChinSyun Pang
Weibo: arthinking_plus
Posted in: http://www.itzhai.com

1、数据呈现

响应式更新(Reactive Updates)

this.props是只读的。

组件就像是函数

React组件非常像函数,接收propsstatus作为参数,然后渲染出HTML。

JSX语法

ReactJS中相信:标签与生成它的代码是紧密相连的,所以不做模板引擎展示逻辑的分离。最好的方法是通过Javascript直接生成模板,为此JSX派上用场了:一个非常简单、可选类似 HTML 语法 ,通过函数调用即可生成模板的编译器。

JSX 让你可以用 HTML 语法去写 JavaScript 函数调用。

1
React.createElement('a', {href: 'http://facebook.github.io/react/'}, 'Hello React!')。

JSX语法:

1
<a href="http://facebook.github.io/react/">Hello React!</a>

1.1、深入理解JSX

JSX是一个看起来像XML的JavaScript语法扩展。React可以用来做简单的JSX句法转换。

1.1.1、为什么要使用JSX

能够定义简洁且我们熟知的包含属性的树状结构语法。

1.1.2、HTML标签与React组件对比

React 的 JSX 里约定分别使用首字母大、小写来区分本地组件的类和 HTML 标签。

1.1.3、转换

1
2
3
4
// JSX
var app = <Nav color="blue" />;
// 等价于
var app = React.createElement(Nav, {color:"blue"});

1
2
3
4
5
6
7
8
9
var Nav, Profile;
// JSX
var app = <Nav color="blue"><Profile>click</Profile></Nav>;
// 等价于
var app = React.createElement(
Nav,
{color:"blue"},
React.createElement(Profile, null, "click")
);

1.1.4、JavaScript表达式

属性表达式

使用JavaScript表达式作为属性值,使用{}

1
2
3
4
5
6
7
// JSX
var person = <Person name={window.isLoggedIn ? window.name : ''} />;
// 等价于
var person = React.createElement(
Person,
{name: window.isLoggedIn ? window.name : ''}
);

子节点表达式

1
2
3
4
5
6
7
8
// JSX
var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;
// 等价于
var content = React.createElement(
Container,
null,
window.isLoggedIn ? React.createElement(Nav) : React.createElement(Login)
);

注释

JSX 里添加注释很容易;它们只是 JS 表达式而已。你只需要在一个标签的子节点内(非最外层)小心地用 {} 包围要注释的部分。

1
2
3
4
5
6
7
8
9
10
11
var content = (
<Nav>
{/* 一般注释, 用 {} 包围 */}
<Person
/* 多

注释 */
name={window.isLoggedIn ? window.name : ''} // 行尾注释
/>

</Nav>
);

1.2、JSX的延展属性

Props 应该被当作禁止修改的。修改 props 对象可能会导致预料之外的结果,所以最好不要去修改 props 对象。

延展属性(Spread Attributes)

这个...操作符(也被叫做延展操作符 - spread operator)已经被 ES6 数组 支持。

1
2
3
var props = { foo: 'default' };
var component = <Component {...props} foo={'override'} />;
console.log(component.props.foo); // 'override'

1.3、JSX的陷阱

HTML实体

如果想在 JSX 表达式中显示 HTML 实体,可以会遇到二次转义的问题,因为 React 默认会转义所有字符串,为了防止各种 XSS 攻击。

1
2
// 错误: 会显示 “First &middot; Second”
<div>{'First &middot; Second'}</div>

解决方法:

  • 直接使用Unicode字符串
1
<div>{'First · Second'}</div>
  • 使用Unicode编号
1
2
<div>{'First \u00b7 Second'}</div>
<div>{'First ' + String.fromCharCode(183) + ' Second'}</div>
  • 在数组里面使用字符串和JSX元素
1
<div>{['First ', <span>&middot;</span>, ' Second']}</div>
  • 使用原始HTML
1
<div dangerouslySetInnerHTML={{__html: 'First &middot; Second'}} />

自定义HTML属性

React里面如果需要使用自定义属性,要加 data- 前缀。

1
<div data-custom-attribute="foo" />

2、富交互性的动态用户界面

2.1、事件处理与合成事件(Synthetic Events)

1
React.initializeTouchEvents(true); 启用触摸事件处理。

2.2、幕后原理:自动绑定和事件代理

在幕后,React 做了一些操作来让代码高效运行且易于理解。

Autobinding: 在 JavaScript 里创建回调的时候,为了保证 this 的正确性,一般都需要显式地绑定方法到它的实例上。有了 React,所有方法被自动绑定到了它的组件实例上。

事件代理: React 实际并没有把事件处理器绑定到节点本身。

2.3、组件其实是状态机(State Machines)

React 把用户界面当作简单状态机。把用户界面想像成拥有不同状态然后渲染这些状态,可以轻松让用户界面和数据保持一致。

React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。React 来决定如何最高效地更新 DOM。

2.4、State 工作原理

常用的通知 React 数据变化的方法是调用 setState(data, callback)。这个方法会合并(merge) data 到 this.state,并重新渲染组件。渲染完成后,调用可选的 callback 回调。

2.5、哪些组件应该有 State?

有时需要对用户输入、服务器请求或者时间变化等作出响应,这时才需要使用 State。

2.6、哪些 应该 作为 State?

State 应该包括那些可能被组件的事件处理器改变并触发用户界面更新的数据。

2.7、哪些 不应该 作为 State?

this.state 应该仅包括能表示用户界面状态所需的最少数据。因此,它不应该包括:

  • 计算所得数据
  • React组件
  • 基于props的重复数据

3、复合组件

3.1、动机:关注分离

通过复用那些接口定义良好的组件来开发新的模块化组件,我们得到了与使用函数和类相似的好处。

3.2、组合实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var Avatar = React.createClass({
render: function() {
return (
<div>
<ProfilePic username={this.props.username} />
<ProfileLink username={this.props.username} />
</div>
);
}
});

var ProfilePic = React.createClass({
render: function() {
return (
<img src={'http://graph.facebook.com/' + this.props.username + '/picture'} />
);
}
});

var ProfileLink = React.createClass({
render: function() {
return (
<a href={'http://www.facebook.com/' + this.props.username}>
{this.props.username}
</a>
);
}
});

React.render(
<Avatar username="pwh" />,
document.getElementById('example')
);

3.3、从属关系

组件不能修改自身的 props - 它们总是与它们拥有者设置的保持一致。这是保持用户界面一致性的关键性原则。

3.4、子级

实例化 React 组件时,你可以在开始标签和结束标签之间引用在React 组件或者Javascript 表达式:

1
<Parent><Child /></Parent>

Parent能通过专门的this.props.children props读取子级。this.props.children是一个不透明的数据结构: 通过React.Children工具类 来操作。

3.5、子级校正(Reconciliation)

校正就是每次 render 方法调用后 React 更新 DOM 的过程。

1
2
3
4
5
6
7
8
9
// 第一次渲染
<Card>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</Card>
// 第二次渲染
<Card>
<p>Paragraph 2</p>
</Card>

React 是根据子级的顺序来校正的。

3.6、子组件状态管理

对于使用 this.state 来在多次渲染过程中里维持数据的状态化组件,上面例子这样做潜在很多问题。

多数情况下,可以通过隐藏组件而不是删除它们来绕过这些问题。

3.7、动态子级

如果子组件位置会改变(如在搜索结果中)或者有新组件添加到列表开头(如在流中)情况会变得更加复杂。

果子级要在多个渲染阶段保持自己的特征和状态,在这种情况下,你可以通过给子级设置惟一标识的 key 来区分。

1
2
3
4
5
6
7
8
9
10
render: function() {
var results = this.props.results;
return (
<ol>
{results.map(function(result) {
return <li key={result.id}>{result.text}</li>;
})}
</ol>

);
}

务必 把 key 添加到子级数组里组件本身上,而不是每个子级内部最外层 HTML 上。

也可以传递 object 来做有 key 的子级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
render: function() {
var items = {};

this.props.results.forEach(function(result) {
// 如果 result.id 看起来是一个数字(比如短哈希),那么
// 对象字面量的顺序就得不到保证。这种情况下,需要添加前缀
// 来确保 key 是字符串。
items['result-' + result.id] = <li>{result.text}</li>;
});

return (
<ol>
{items}
</ol>

);
}

3.8、数据流

数据通过上面介绍过的 props 从拥有者流向归属者。这就是高效的单向数据绑定(one-way data binding)

3.9、性能提醒

能的瓶颈大多是因为 DOM 更新,而非 JS 执行,而且 React 会通过批量更新和变化检测来优化性能。

有时候需要做细粒度的性能控制。这种情况下,可以重写 shouldComponentUpdate() 方法返回 false 来让 React 跳过对子树的处理。

不要低估 JavaScript 的速度,DOM 操作通常才是慢的原因。

自己实现一个单向数据绑定?

4、可复用组件

把通用的设计元素(按钮,表单框,布局组件等)拆成接口良好定义的可复用的组件。

4.1、Prop 验证

React.PropTypes 提供很多验证器 (validator) 来验证传入数据的有效性。当向 props 传入无效数据时,JavaScript 控制台会抛出警告。注意为了性能考虑,只在开发环境验证 propTypes。

4.2、默认 Prop 值

1
2
3
4
5
6
7
8
var ComponentWithDefaultProps = React.createClass({
getDefaultProps: function() {
return {
value: 'default value'
};
}
/* ... */
});

当父级没有传入 props 时,getDefaultProps() 可以保证 this.props.value 有默认值,注意 getDefaultProps 的结果会被 缓存。得益于此,你可以直接使用 props,而不必写手动编写一些重复或无意义的代码。

4.3、传递 Props:小技巧

JSX 的 spread 语法

1
2
3
4
5
6
7
8
9
10
11
12
13
var CheckLink = React.createClass({
render: function() {
// 这样会把 CheckList 所有的 props 复制到 <a>
return <a {...this.props}>{'√ '}{this.props.children}</a>;
}
});

React.render(
<CheckLink href="/checked.html">
Click here!
</CheckLink>,

document.getElementById('example')
);

4.4、单个子级

React.PropTypes.element 可以限定只能有一个子级传入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var MyComponent = React.createClass({
propTypes: {
children: React.PropTypes.element.isRequired
},

render: function() {
return (
<div>
{this.props.children} // 有且仅有一个元素,否则会抛异常。
</div>

);
}

});

4.5、Mixins

有时一些复杂的组件间也需要共用一些功能。有时会被称为跨切面关注点。React 使用mixins来解决这类问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var SetIntervalMixin = {
componentWillMount: function() {
this.intervals = [];
},
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() {
this.intervals.map(clearInterval);
}
};

var TickTock = React.createClass({
mixins: [SetIntervalMixin], // 引用 mixin
getInitialState: function() {
return {seconds: 0};
},
componentDidMount: function() {
this.setInterval(this.tick, 1000); // 调用 mixin 的方法
},
tick: function() {
this.setState({seconds: this.state.seconds + 1});
},
render: function() {
return (
<p>
React has been running for {this.state.seconds} seconds.
</p>

);
}
});

React.render(
<TickTock />,
document.getElementById('example')
);

5、传递Props

可以使用 JSX 展开属性 来合并现有的 props 和其它值:

1
return <Component {...this.props} more="values" />;

也可以使用一些对象辅助方法如 ES6 的 Object.assign 或 Underscore _.extend。

1
return Component(Object.assign({}, this.props, { more: 'values' }));

5.1、手动传递

大部分情况下你应该显式地向下传递 props。这样可以确保只公开你认为是安全的内部 API 的子集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 var FancyCheckbox = React.createClass({
render: function() {
var fancyClass = this.props.checked ? 'FancyChecked' : 'FancyUnchecked';
return (
<div className={fancyClass} onClick={this.props.onClick}>
{this.props.children}
</div>

);
}
});
React.render(
<FancyCheckbox checked={true} onClick={console.log.bind(console)}>
Hello world!
</FancyCheckbox>,

document.body
);

5.2、在 JSX 里使用 … 传递

有时把所有属性都传下去是不安全或啰嗦的。这时可以使用解构赋值中的剩余属性特性来把未知属性批量提取出来。

列出所有要当前使用的属性,后面跟着...other

1
var { checked, ...other } = this.props;

这样能确保把所有 props 传下去,除了 那些已经被使用了的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var FancyCheckbox = React.createClass({
render: function() {
var { checked, ...other } = this.props;
var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked';
// `other` 包含 { onClick: console.log } 但 checked 属性除外
return (
<div {...other} className={fancyClass} /> // 继续往下传递其他的属性
);
}
});
React.render(
<FancyCheckbox checked={true} onClick={console.log.bind(console)}>
Hello world!
</FancyCheckbox>,
document.body
);

在传递这些未知的other属性时,要经常使用解构赋值模式。

5.3、使用和传递同一个 Prop

如果组件需要使用一个属性又要往下传递,可以直接使用checked={checked}再传一次。这样做比传整个this.props对象要好,因为更利于重构和语法检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var FancyCheckbox = React.createClass({
render: function() {
var { checked, title, ...other } = this.props;
var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked';
var fancyTitle = checked ? 'X ' + title : 'O ' + title;
return (
<label>
<input {...other} // 顺序很重要,把 {...other} 放到 JSX props 前面会使它不被覆盖。
checked={checked}
className={fancyClass}
type="checkbox"
/>

{fancyTitle}
</label>
);
}
});

5.4、剩余属性和展开属性 …

剩余属性可以把对象剩下的属性提取到一个新的对象。会把所有在解构赋值中列出的属性剔除。

这是 ES7 草案 中的试验特性。

1
2
3
4
var { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 }

使用 JSX 命令行工具 配合 –harmony 标记来启用 ES7 语法。

5.5、使用 Underscore 来传递

在不适用JSX的情况下,可以使用一些库来实现相同的效果,如Undersocre

6、表单组件

表单事件:onChange onInput onSubmit

6.1、交互属性

表单组件支持几个受用户交互影响的属性:

  • value,用于
  • checked,用于类型为 checkbox 或者 radio 的 组件。
  • selected,用于

6.2、受限组件

设置了 value 的 是一个受限组件。相当于设置了只读属性。

如果想响应更新用户输入的值,就得使用 onChange 事件:

1
2
3
4
5
6
7
8
9
10
getInitialState: function() {
return {value: 'Hello!'};
},
handleChange: function(event) {
this.setState({value: event.target.value});
},
render: function() {
var value = this.state.value;
return <input type="text" value={value} onChange={this.handleChange} />;
}

6.3、不受限组件

没有设置 value(或者设为 null) 的 组件是一个不受限组件。

如果想给组件设置一个非空的初始值,可以使用 defaultValue 属性。

6.4、高级主题

为什么使用受限组件?

在 HTML 中将渲染初始值为 Untitled 的输入框。用户改变输入框的值时,节点的 value 属性(property)将随之变化,但是 node.getAttribute(‘value’) 还是会返回初始设置的值 Untitled.

与 HTML 不同,React 组件必须在任何时间点描绘视图的状态,而不仅仅是在初始化时。比如在 React 中:

1
2
3
render: function() {
return <input type="text" name="title" value="Untitled" />;
}

该方法在任何时间点渲染组件以后,输入框的值就应该始终为 Untitled。

为什么

React 是 JavaScript,没有字符限制,可以使用 \n 实现换行。简言之,React 已经有 value、defaultValue 属性, 组件的子

如果非要*使用子节点,效果和使用 defaultValue 一样。

为什么

React 为了更方面的控制组件。

给 value 属性传递一个数组,可以选中多个选项:

7、浏览器中的工作原理

React提供了强大的抽象,让你在大多数应用场景中不再直接操作DOM,但是有时你需要简单地调用底层的API,或者借助于第三方库或已有的代码。

7.1、虚拟DOM

React是很快的,因为它从不直接操作DOM。React在内存中维护一个快速响应的DOM描述。render()方法返回一个DOM的描述,React能够利用内存中的描述来快速地计算出差异,然后更新浏览器中的DOM。

有时你简单地需要调用底层的API,或许借助于第三方的类似于jQuery插件这种库。React为你提供了直接使用底层DOM API的途径。

7.2、Refs和getDOMNode()

每一个挂载的React组件有一个getDOMNode()方法,你可以调用这个方法来获取对该节点的引用。

getDOMNode()仅在挂载的组件上有效。在创建组件的render()函数中调用getDOMNode(),将会抛出异常。

为了获取一个到React组件的引用,你可以使用this来得到当前的React组件,或者你可以使用refs来指向一个你拥有的组件。

7.3、组件生命周期

  • 挂载: 组件被插入到DOM中。
  • 更新: 组件被重新渲染,查明DOM是否应该刷新。
  • 移除: 组件从DOM中移除。

挂载

getInitialState() object在组件被挂载之前调用。状态化的组件应该实现这个方法,返回初始的state数据。
componentWillMount() 在挂载发生之前立即被调用。
componentDidMount() 在挂载结束之后马上被调用。需要DOM节点的初始化操作应该放在这里。

更新

componentWillReceiveProps(object nextProps)当一个挂载的组件接收到新的props的时候被调用。该方法应该用于比较this.props和nextProps,然后使用this.setState()来改变state。
shouldComponentUpdate(object nextProps, object nextState) boolean当组件做出是否要更新DOM的决定的时候被调用。实现该函数,优化this.props和nextProps,以及this.state和nextState的比较,如果不需要React更新DOM,则返回false。
componentWillUpdate(object nextProps, object nextState) 在更新发生之前被调用。你可以在这里调用this.setState()。
componentDidUpdate(object prevProps, object prevState) 在更新发生之后调用。

移除

componentWillUnmount()在组件移除和销毁之前被调用。清理工作应该放在这里。

挂载的方法(Mounted Methods)

挂载的复合组件也支持如下方法:

  • getDOMNode(): DOMElement可以在任何挂载的组件上面调用,用于获取一个指向它的渲染DOM节点的引用。
  • forceUpdate(): 当你知道一些很深的组件state已经改变了的时候,可以在该组件上面调用,而不是使用this.setState()。

7.4、跨浏览器支持和兼容代码

在Facebook,我们支持低版本的浏览器,包括IE8。

8、关于Refs的更多内容

input 元素一些东西 - 这些东西过会儿(onClick,onChange事件后)之后已无法从它的 props 中推算出来。在这个场景中,我们想“通知” input 元素,现在应该处于获取焦点状态。然而,此处有一些障碍。从 render() 中返回的内容并不是你实际创建的子组件的组合,仅仅是一个某一刻某个组件实例的 描述 - 一个快照。

记住,从 render() 中返回的内容并不是实际渲染出来的子组件实例。从 render() 返回的仅仅是子组件层级树实例在特定时间的一个描述。

1
2
3
4
5
6
7
8
9
10
11
// counterexample: DO NOT DO THIS!
render: function() {
var myInput = <input />; // I'm going to try to call methods on this
this.rememberThisInput = myInput; // input at some point in the future! YAY!
return (
<div>
<div>...</div>
{myInput}
</div>
);
}

在这个反例中, 仅仅是一个 组件的描述。该描述用于创建一个真正的 支撑实例( backing instance )。为了获取真正的Input的支撑实力,我们需要使用到ref属性

8.1、ref 属性

React 支持一种非常特殊的属性,你可以用来绑定到 render() 输出的任何组件上去。这个特殊的属性允许你引用 render() 返回的相应的支撑实例( backing instance )。这样就可以确保在任何时间总是拿到正确的实例。

  • 1、绑定一个 ref 属性到 render 返回的东西上面去
1
<input ref="myInput" />
  • 2、在其它代码中(典型地事件处理代码),通过 this.refs 获取支撑实例
1
this.refs.myInput

9、工具集成

9.1、React

9.2、JSX